var isArray = Array.isArray;

  var root = window;

  function EventEmitter() {
  }

  // By default EventEmitters will print a warning if more than
  // 10 listeners are added to it. This is a useful default which
  // helps finding memory leaks.
  //
  // Obviously not all Emitters should be limited to 10. This function allows
  // that to be increased. Set to zero for unlimited.
  var defaultMaxListeners = 10;
  EventEmitter.prototype.setMaxListeners = function (n) {
    if (!this._events) this._events = {};
    this._maxListeners = n;
  };


  EventEmitter.prototype.emit = function () {
    var type = arguments[0];
    // If there is no 'error' event listener then throw.
    if (type === 'error') {
      if (!this._events || !this._events.error ||
        (isArray(this._events.error) && !this._events.error.length)) {
        if (this.domain) {
          var er = arguments[1];
          er.domain_emitter = this;
          er.domain = this.domain;
          er.domain_thrown = false;
          this.domain.emit('error', er);
          return false;
        }

        if (arguments[1] instanceof Error) {
          throw arguments[1]; // Unhandled 'error' event
        } else {
          throw new Error("Uncaught, unspecified 'error' event.");
        }
        return false;
      }
    }

    if (!this._events) return false;
    var handler = this._events[type];
    if (!handler) return false;

    if (typeof handler == 'function') {
      if (this.domain) {
        this.domain.enter();
      }
      switch (arguments.length) {
        // fast cases
        case 1:
          handler.call(this);
          break;
        case 2:
          handler.call(this, arguments[1]);
          break;
        case 3:
          handler.call(this, arguments[1], arguments[2]);
          break;
        // slower
        default:
          var l = arguments.length;
          var args = new Array(l - 1);
          for (var i = 1; i < l; i++) args[i - 1] = arguments[i];
          handler.apply(this, args);
      }
      if (this.domain) {
        this.domain.exit();
      }
      return true;

    } else if (isArray(handler)) {
      if (this.domain) {
        this.domain.enter();
      }
      var l = arguments.length;
      var args = new Array(l - 1);
      for (var i = 1; i < l; i++) args[i - 1] = arguments[i];

      var listeners = handler.slice();
      for (var i = 0, l = listeners.length; i < l; i++) {
        listeners[i].apply(this, args);
      }
      if (this.domain) {
        this.domain.exit();
      }
      return true;

    } else {
      return false;
    }
  };

  EventEmitter.prototype.addListener = function (type, listener) {
    if ('function' !== typeof listener) {
      throw new Error('addListener only takes instances of Function');
    }

    if (!this._events) this._events = {};

    // To avoid recursion in the case that type == "newListeners"! Before
    // adding it to the listeners, first emit "newListeners".
    this.emit('newListener', type, typeof listener.listener === 'function' ?
      listener.listener : listener);

    if (!this._events[type]) {
      // Optimize the case of one listener. Don't need the extra array object.
      this._events[type] = listener;
    } else if (isArray(this._events[type])) {

      // If we've already got an array, just append.
      this._events[type].push(listener);

    } else {
      // Adding the second element, need to change to array.
      this._events[type] = [this._events[type], listener];

    }

    // Check for listener leak
    if (isArray(this._events[type]) && !this._events[type].warned) {
      var m;
      if (this._maxListeners !== undefined) {
        m = this._maxListeners;
      } else {
        m = defaultMaxListeners;
      }

      if (m && m > 0 && this._events[type].length > m) {
        this._events[type].warned = true;
        console.error('(node) warning: possible EventEmitter memory ' +
          'leak detected. %d listeners added. ' +
          'Use emitter.setMaxListeners() to increase limit.',
          this._events[type].length);
        console.trace();
      }
    }

    return this;
  };

  EventEmitter.prototype.on = EventEmitter.prototype.addListener;

  EventEmitter.prototype.once = function (type, listener) {
    if ('function' !== typeof listener) {
      throw new Error('.once only takes instances of Function');
    }

    var self = this;
    function g() {
      self.removeListener(type, g);
      listener.apply(this, arguments);
    };

    g.listener = listener;
    self.on(type, g);

    return this;
  };

  EventEmitter.prototype.removeListener = function (type, listener) {
    if ('function' !== typeof listener) {
      throw new Error('removeListener only takes instances of Function');
    }

    // does not use listeners(), so no side effect of creating _events[type]
    if (!this._events || !this._events[type]) return this;

    var list = this._events[type];

    if (isArray(list)) {
      var position = -1;
      for (var i = 0, length = list.length; i < length; i++) {
        if (list[i] === listener ||
          (list[i].listener && list[i].listener === listener)) {
          position = i;
          break;
        }
      }

      if (position < 0) return this;
      list.splice(position, 1);
    } else if (list === listener ||
      (list.listener && list.listener === listener)) {
      delete this._events[type];
    }

    return this;
  };

  EventEmitter.prototype.removeAllListeners = function (type) {
    if (arguments.length === 0) {
      this._events = {};
      return this;
    }

    var events = this._events && this._events[type];
    if (!events) return this;

    if (isArray(events)) {
      events.splice(0);
    } else {
      this._events[type] = null;
    }

    return this;
  };

  EventEmitter.prototype.listeners = function (type) {
    if (!this._events) this._events = {};
    if (!this._events[type]) this._events[type] = [];
    if (!isArray(this._events[type])) {
      this._events[type] = [this._events[type]];
    }
    return this._events[type];
  }


  var Protocol = {};

  var HEADER = 5;

  var Message = function (id, route, body) {
    this.id = id;
    this.route = route;
    this.body = body;
  };

  /**
   *
   *pomele client encode
   * id message id;
   * route message route
   * msg message body
   * socketio current support string
   *
   */
  Protocol.encode = function (id, route, msg) {
    var msgStr = JSON.stringify(msg);
    if (route.length > 255) { throw new Error('route maxlength is overflow'); }
    var byteArray = new Uint16Array(HEADER + route.length + msgStr.length);
    var index = 0;
    byteArray[index++] = (id >> 24) & 0xFF;
    byteArray[index++] = (id >> 16) & 0xFF;
    byteArray[index++] = (id >> 8) & 0xFF;
    byteArray[index++] = id & 0xFF;
    byteArray[index++] = route.length & 0xFF;
    for (var i = 0; i < route.length; i++) {
      byteArray[index++] = route.charCodeAt(i);
    }
    for (var i = 0; i < msgStr.length; i++) {
      byteArray[index++] = msgStr.charCodeAt(i);
    }
    return bt2Str(byteArray, 0, byteArray.length);
  };




  /**
   *
   *client decode
   *msg String data
   *return Message Object
   */
  Protocol.decode = function (msg) {
    var idx, len = msg.length, arr = new Array(len);
    for (idx = 0; idx < len; ++idx) {
      arr[idx] = msg.charCodeAt(idx);
    }
    var index = 0;
    var buf = new Uint16Array(arr);
    var id = ((buf[index++] << 24) | (buf[index++]) << 16 | (buf[index++]) << 8 | buf[index++]) >>> 0;
    var routeLen = buf[HEADER - 1];
    var route = bt2Str(buf, HEADER, routeLen + HEADER);
    var body = bt2Str(buf, routeLen + HEADER, buf.length);
    return new Message(id, route, body);
  };

  var bt2Str = function (byteArray, start, end) {
    var result = "";
    for (var i = start; i < byteArray.length && i < end; i++) {
      result = result + String.fromCharCode(byteArray[i]);
    };
    return result;
  }


  // pomelo
  if (typeof Object.create !== 'function') {
    Object.create = function (o) {
      function F() { }
      F.prototype = o;
      return new F();
    };
  }

  var root = window;
  var pomelo = Object.create(EventEmitter.prototype); // object extend from object
  root.pomelo = pomelo;
  var socket = null;
  var id = 1;
  var callbacks = {};

  pomelo.init = function (params, cb) {
    pomelo.params = params;
    params.debug = true;
    var host = params.host;
    var port = params.port;

    var url = 'ws://' + host;
    if (port) {
      url += ':' + port;
    }

    socket = io(url, { 'force new connection': true, reconnect: false });

    socket.on('connect', function () {
      console.log('[pomeloclient.init] websocket connected!');
      if (cb) {
        cb(socket);
      }
    });

    socket.on('reconnect', function () {
      console.log('reconnect');
    });

    socket.on('message', function (data) {
      if (typeof data === 'string') {
        data = JSON.parse(data);
      }
      if (data instanceof Array) {
        processMessageBatch(pomelo, data);
      } else {
        processMessage(pomelo, data);
      }
    });

    socket.on('error', function (err) {
      console.log(err);
    });

    socket.on('disconnect', function (reason) {
      pomelo.emit('disconnect', reason);
    });
  };

  pomelo.disconnect = function () {
    if (socket) {
      socket.disconnect();
      socket = null;
    }
  };

  pomelo.request = function (...arg) {
    const route = arg[0];
    if (!route) {
      return;
    }
    var msg = {};
    var cb;
    // const arg = Array.prototype.slice.apply(arguments);
    if (arg.length === 2) {
      if (typeof arg[1] === 'function') {
        cb = arg[1];
      } else if (typeof arg[1] === 'object') {
        msg = arg[1];
      }
    } else if (arg.length === 3) {
      msg = arg[1];
      cb = arg[2];
    }
    msg = filter(msg, route);
    id++;
    callbacks[id] = cb;
    var sg = Protocol.encode(id, route, msg);
    socket.send(sg);
  };

  pomelo.notify = function (route, msg) {
    this.request(route, msg);
  };

  var processMessage = function (pomelo, msg) {
    var route;
    if (msg.id) {
      //if have a id then find the callback function with the request
      var cb = callbacks[msg.id];

      delete callbacks[msg.id];
      if (typeof cb !== 'function') {
        // console.log('[pomeloclient.processMessage] cb is not a function for request ' + msg.id);
        return;
      }

      cb(msg.body);
      return;
    }

    // server push message or old format message
    processCall(msg);

    //if no id then it should be a server push message
    function processCall(msg) {
      var route = msg.route;
      if (!!route) {
        if (!!msg.body) {
          var body = msg.body.body;
          if (!body) { body = msg.body; }
          pomelo.emit(route, body);
        } else {
          pomelo.emit(route, msg);
        }
      } else {
        pomelo.emit(msg.body.route, msg.body);
      }
    }
  };

  var processMessageBatch = function (pomelo, msgs) {
    for (var i = 0, l = msgs.length; i < l; i++) {
      processMessage(pomelo, msgs[i]);
    }
  };

  function filter(msg, route) {
    if (route.indexOf('area.') === 0) {
      msg.areaId = pomelo.areaId;
    }

    msg.timestamp = Date.now();
    return msg;
  }


  export default pomelo;





// // (function () {
// //   var isArray = Array.isArray;

// //   var root = this;

// //   function EventEmitter() {
// //   }


// //   if (typeof module !== 'undefined' && module.exports) {
// //     module.exports.EventEmitter = EventEmitter;
// //   }
// //   else {
// //     root = window;
// //     root.EventEmitter = EventEmitter;
// //   }

// //   // By default EventEmitters will print a warning if more than
// //   // 10 listeners are added to it. This is a useful default which
// //   // helps finding memory leaks.
// //   //
// //   // Obviously not all Emitters should be limited to 10. This function allows
// //   // that to be increased. Set to zero for unlimited.
// //   var defaultMaxListeners = 10;
// //   EventEmitter.prototype.setMaxListeners = function (n) {
// //     if (!this._events) this._events = {};
// //     this._maxListeners = n;
// //   };


// //   EventEmitter.prototype.emit = function () {
// //     var type = arguments[0];
// //     // If there is no 'error' event listener then throw.
// //     if (type === 'error') {
// //       if (!this._events || !this._events.error ||
// //         (isArray(this._events.error) && !this._events.error.length)) {
// //         if (this.domain) {
// //           var er = arguments[1];
// //           er.domain_emitter = this;
// //           er.domain = this.domain;
// //           er.domain_thrown = false;
// //           this.domain.emit('error', er);
// //           return false;
// //         }

// //         if (arguments[1] instanceof Error) {
// //           throw arguments[1]; // Unhandled 'error' event
// //         } else {
// //           throw new Error("Uncaught, unspecified 'error' event.");
// //         }
// //         return false;
// //       }
// //     }

// //     if (!this._events) return false;
// //     var handler = this._events[type];
// //     if (!handler) return false;

// //     if (typeof handler == 'function') {
// //       if (this.domain) {
// //         this.domain.enter();
// //       }
// //       switch (arguments.length) {
// //         // fast cases
// //         case 1:
// //           handler.call(this);
// //           break;
// //         case 2:
// //           handler.call(this, arguments[1]);
// //           break;
// //         case 3:
// //           handler.call(this, arguments[1], arguments[2]);
// //           break;
// //         // slower
// //         default:
// //           var l = arguments.length;
// //           var args = new Array(l - 1);
// //           for (var i = 1; i < l; i++) args[i - 1] = arguments[i];
// //           handler.apply(this, args);
// //       }
// //       if (this.domain) {
// //         this.domain.exit();
// //       }
// //       return true;

// //     } else if (isArray(handler)) {
// //       if (this.domain) {
// //         this.domain.enter();
// //       }
// //       var l = arguments.length;
// //       var args = new Array(l - 1);
// //       for (var i = 1; i < l; i++) args[i - 1] = arguments[i];

// //       var listeners = handler.slice();
// //       for (var i = 0, l = listeners.length; i < l; i++) {
// //         listeners[i].apply(this, args);
// //       }
// //       if (this.domain) {
// //         this.domain.exit();
// //       }
// //       return true;

// //     } else {
// //       return false;
// //     }
// //   };

// //   EventEmitter.prototype.addListener = function (type, listener) {
// //     if ('function' !== typeof listener) {
// //       throw new Error('addListener only takes instances of Function');
// //     }

// //     if (!this._events) this._events = {};

// //     // To avoid recursion in the case that type == "newListeners"! Before
// //     // adding it to the listeners, first emit "newListeners".
// //     this.emit('newListener', type, typeof listener.listener === 'function' ?
// //       listener.listener : listener);

// //     if (!this._events[type]) {
// //       // Optimize the case of one listener. Don't need the extra array object.
// //       this._events[type] = listener;
// //     } else if (isArray(this._events[type])) {

// //       // If we've already got an array, just append.
// //       this._events[type].push(listener);

// //     } else {
// //       // Adding the second element, need to change to array.
// //       this._events[type] = [this._events[type], listener];

// //     }

// //     // Check for listener leak
// //     if (isArray(this._events[type]) && !this._events[type].warned) {
// //       var m;
// //       if (this._maxListeners !== undefined) {
// //         m = this._maxListeners;
// //       } else {
// //         m = defaultMaxListeners;
// //       }

// //       if (m && m > 0 && this._events[type].length > m) {
// //         this._events[type].warned = true;
// //         console.error('(node) warning: possible EventEmitter memory ' +
// //           'leak detected. %d listeners added. ' +
// //           'Use emitter.setMaxListeners() to increase limit.',
// //           this._events[type].length);
// //         console.trace();
// //       }
// //     }

// //     return this;
// //   };

// //   EventEmitter.prototype.on = EventEmitter.prototype.addListener;

// //   EventEmitter.prototype.once = function (type, listener) {
// //     if ('function' !== typeof listener) {
// //       throw new Error('.once only takes instances of Function');
// //     }

// //     var self = this;
// //     function g() {
// //       self.removeListener(type, g);
// //       listener.apply(this, arguments);
// //     };

// //     g.listener = listener;
// //     self.on(type, g);

// //     return this;
// //   };

// //   EventEmitter.prototype.removeListener = function (type, listener) {
// //     if ('function' !== typeof listener) {
// //       throw new Error('removeListener only takes instances of Function');
// //     }

// //     // does not use listeners(), so no side effect of creating _events[type]
// //     if (!this._events || !this._events[type]) return this;

// //     var list = this._events[type];

// //     if (isArray(list)) {
// //       var position = -1;
// //       for (var i = 0, length = list.length; i < length; i++) {
// //         if (list[i] === listener ||
// //           (list[i].listener && list[i].listener === listener)) {
// //           position = i;
// //           break;
// //         }
// //       }

// //       if (position < 0) return this;
// //       list.splice(position, 1);
// //     } else if (list === listener ||
// //       (list.listener && list.listener === listener)) {
// //       delete this._events[type];
// //     }

// //     return this;
// //   };

// //   EventEmitter.prototype.removeAllListeners = function (type) {
// //     if (arguments.length === 0) {
// //       this._events = {};
// //       return this;
// //     }

// //     var events = this._events && this._events[type];
// //     if (!events) return this;

// //     if (isArray(events)) {
// //       events.splice(0);
// //     } else {
// //       this._events[type] = null;
// //     }

// //     return this;
// //   };

// //   EventEmitter.prototype.listeners = function (type) {
// //     if (!this._events) this._events = {};
// //     if (!this._events[type]) this._events[type] = [];
// //     if (!isArray(this._events[type])) {
// //       this._events[type] = [this._events[type]];
// //     }
// //     return this._events[type];
// //   }
// // })();

// // (function (exports, global) {
// //   var Protocol = exports;

// //   var HEADER = 5;

// //   var Message = function (id, route, body) {
// //     this.id = id;
// //     this.route = route;
// //     this.body = body;
// //   };

// //   /**
// //    *
// //    *pomele client encode
// //    * id message id;
// //    * route message route
// //    * msg message body
// //    * socketio current support string
// //    *
// //    */
// //   Protocol.encode = function (id, route, msg) {
// //     var msgStr = JSON.stringify(msg);
// //     if (route.length > 255) { throw new Error('route maxlength is overflow'); }
// //     var byteArray = new Uint16Array(HEADER + route.length + msgStr.length);
// //     var index = 0;
// //     byteArray[index++] = (id >> 24) & 0xFF;
// //     byteArray[index++] = (id >> 16) & 0xFF;
// //     byteArray[index++] = (id >> 8) & 0xFF;
// //     byteArray[index++] = id & 0xFF;
// //     byteArray[index++] = route.length & 0xFF;
// //     for (var i = 0; i < route.length; i++) {
// //       byteArray[index++] = route.charCodeAt(i);
// //     }
// //     for (var i = 0; i < msgStr.length; i++) {
// //       byteArray[index++] = msgStr.charCodeAt(i);
// //     }
// //     return bt2Str(byteArray, 0, byteArray.length);
// //   };




// //   /**
// //    *
// //    *client decode
// //    *msg String data
// //    *return Message Object
// //    */
// //   Protocol.decode = function (msg) {
// //     var idx, len = msg.length, arr = new Array(len);
// //     for (idx = 0; idx < len; ++idx) {
// //       arr[idx] = msg.charCodeAt(idx);
// //     }
// //     var index = 0;
// //     var buf = new Uint16Array(arr);
// //     var id = ((buf[index++] << 24) | (buf[index++]) << 16 | (buf[index++]) << 8 | buf[index++]) >>> 0;
// //     var routeLen = buf[HEADER - 1];
// //     var route = bt2Str(buf, HEADER, routeLen + HEADER);
// //     var body = bt2Str(buf, routeLen + HEADER, buf.length);
// //     return new Message(id, route, body);
// //   };

// //   var bt2Str = function (byteArray, start, end) {
// //     var result = "";
// //     for (var i = start; i < byteArray.length && i < end; i++) {
// //       result = result + String.fromCharCode(byteArray[i]);
// //     };
// //     return result;
// //   }

// // })('object' === typeof module ? module.exports : (this.Protocol = {}), this);

// // (function () {
// //   if (typeof Object.create !== 'function') {
// //     Object.create = function (o) {
// //       function F() { }
// //       F.prototype = o;
// //       return new F();
// //     };
// //   }

// //   var root = window;
// //   var pomelo = Object.create(EventEmitter.prototype); // object extend from object
// //   root.pomelo = pomelo;
// //   var socket = null;
// //   var id = 1;
// //   var callbacks = {};

// //   pomelo.init = function (params, cb) {
// //     pomelo.params = params;
// //     params.debug = true;
// //     var host = params.host;
// //     var port = params.port;

// //     var url = 'ws://' + host;
// //     if (port) {
// //       url += ':' + port;
// //     }

// //     socket = io(url, { 'force new connection': true, reconnect: false });

// //     socket.on('connect', function () {
// //       console.log('[pomeloclient.init] websocket connected!');
// //       if (cb) {
// //         cb(socket);
// //       }
// //     });

// //     socket.on('reconnect', function () {
// //       console.log('reconnect');
// //     });

// //     socket.on('message', function (data) {
// //       if (typeof data === 'string') {
// //         data = JSON.parse(data);
// //       }
// //       if (data instanceof Array) {
// //         processMessageBatch(pomelo, data);
// //       } else {
// //         processMessage(pomelo, data);
// //       }
// //     });

// //     socket.on('error', function (err) {
// //       console.log(err);
// //     });

// //     socket.on('disconnect', function (reason) {
// //       pomelo.emit('disconnect', reason);
// //     });
// //   };

// //   pomelo.disconnect = function () {
// //     if (socket) {
// //       socket.disconnect();
// //       socket = null;
// //     }
// //   };

// //   pomelo.request = function (route) {
// //     if (!route) {
// //       return;
// //     }
// //     var msg = {};
// //     var cb;
// //     arguments = Array.prototype.slice.apply(arguments);
// //     if (arguments.length === 2) {
// //       if (typeof arguments[1] === 'function') {
// //         cb = arguments[1];
// //       } else if (typeof arguments[1] === 'object') {
// //         msg = arguments[1];
// //       }
// //     } else if (arguments.length === 3) {
// //       msg = arguments[1];
// //       cb = arguments[2];
// //     }
// //     msg = filter(msg, route);
// //     id++;
// //     callbacks[id] = cb;
// //     var sg = Protocol.encode(id, route, msg);
// //     socket.send(sg);
// //   };

// //   pomelo.notify = function (route, msg) {
// //     this.request(route, msg);
// //   };

// //   var processMessage = function (pomelo, msg) {
// //     var route;
// //     if (msg.id) {
// //       //if have a id then find the callback function with the request
// //       var cb = callbacks[msg.id];

// //       delete callbacks[msg.id];
// //       if (typeof cb !== 'function') {
// //         console.log('[pomeloclient.processMessage] cb is not a function for request ' + msg.id);
// //         return;
// //       }

// //       cb(msg.body);
// //       return;
// //     }

// //     // server push message or old format message
// //     processCall(msg);

// //     //if no id then it should be a server push message
// //     function processCall(msg) {
// //       var route = msg.route;
// //       if (!!route) {
// //         if (!!msg.body) {
// //           var body = msg.body.body;
// //           if (!body) { body = msg.body; }
// //           pomelo.emit(route, body);
// //         } else {
// //           pomelo.emit(route, msg);
// //         }
// //       } else {
// //         pomelo.emit(msg.body.route, msg.body);
// //       }
// //     }
// //   };

// //   var processMessageBatch = function (pomelo, msgs) {
// //     for (var i = 0, l = msgs.length; i < l; i++) {
// //       processMessage(pomelo, msgs[i]);
// //     }
// //   };

// //   function filter(msg, route) {
// //     if (route.indexOf('area.') === 0) {
// //       msg.areaId = pomelo.areaId;
// //     }

// //     msg.timestamp = Date.now();
// //     return msg;
// //   }


// // })();

